package adaptiveIntegration.concurrente;
/* $Id: AdapInt.java 2844 2004-11-24 10:52:27Z ceriel $ */

/**
 * Esta versión nace de la versión secuencial:
 * adaptiveIntegration.modificado.AdapInt
 */

/** 
 * Imports relacionados con esta versión concurrente 
 */
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * Implementamos la interfaz Callable, parametrizada con Double ya que será 
 * el tipo del objeto de retorno de los hilos.
 */
final class AdapInt implements Callable<Double> {

	static final double EPSILON = 0.0001;

	/**
	 * Se vuelve a utilizar un umbral, al igual que en la versión 
	 * gridificada. Con este umbral se evita paralelizar procesamiento 
	 * trivial.
	 */
	static final double THRESHOLD = 10000000.0 * EPSILON;

	/**
	 * Utilizo un solo pool de threads para todas las instancias. 
	 * <ul>
	 * 	<li>
	 * 		Un pool fijo (newFixedThreadPool(2)) para cada instancia de 
	 * 		AdapInt (resultado: OutOfMemory)
	 * 	</li>
	 * 	<li>
	 * 		Un pool simple (newSingleThreadExecutor()) para cada tarea 
	 * 		agregada (resultado: OutOfMemory)
	 * 	</li>
	 * 	<li>
	 * 		Un pool único (static) fijo (newFixedThreadPool(2)) para todas 
	 * 		las instancias (resultado: No termina).
	 * 	</li>
	 *  <li>
	 * 		Un pool único (static) dinámico (newCachedThreadPool()) para 
	 * 		todas las instancias (resultado: Funciona).
	 * 	</li>
	 * </ul>
	 * Obviamente tienen sentidos las pruebas ya que:
	 * Crear mas de un pool es erróneo.
	 * Crear un pool que ejecuta sólo un número definido de hilos no 
	 * funciona en este tipo de algoritmos donde la recursión debe llegar 
	 * hasta las hojas.
	 */
	private static ExecutorService pool=null;

	/**
	 * Este método encapsula el proceso de creación de un objeto AdapInt y
	 * su agregado al pool. Retorna el Future del hilo.
	 * @return Future para poder hacer get().
	 */
	private static Future<Double> execute(
			double a, double b, double epsilon) {

		Callable<Double> adapt = new AdapInt(a, b, epsilon);
		if (pool == null) {
			pool = Executors.newCachedThreadPool();
			//pool = Executors.newFixedThreadPool(2);
		}
		//return Executors.newSingleThreadExecutor().submit(adapt);
		return pool.submit(adapt);
	}

	/**
	 * Variables de instancia que encapsulan los valores de entrada del 
	 * algoritmo. Permitirán llamar al call() sin parámetros.
	 */
	private double myA;
	private double myB;
	private double myEpsilon;

	/**
	 * El creador ahora recibe los valores a procesar. Se almacenan esos 
	 * parámetros en las variables de instancia.
	 * @param a
	 * @param b
	 * @param epsilon
	 */
	public AdapInt(double a, double b, double epsilon) {
		this.myA = a;
		this.myB = b;
		this.myEpsilon = epsilon;
	}

	/**
	 * call() heredado de Callable. Llama al método que se hizo concurrente.
	 */
	@Override
	public Double call() throws Exception {
		return integrate(this.myA, this.myB, this.myEpsilon);
	}

	static double f(double x) {
		return Math.sin(x) * 0.1 * x;
	}


	/**
	 * Ahora retorna un objeto Double (en vez de double).
	 * Se agrega el "throws" de las excepciones que puede llegar a lanzar
	 * el get() de los Future's.
	 */
	public Double integrate(double a, double b, double epsilon) 
	throws InterruptedException, ExecutionException {
		double total, left, right, diff;
		double delta, deltahalf, mid;
		double fa, fb, fmid;


		delta = (b - a) / 2.0;
		deltahalf = delta / 2.0;
		mid = delta + a;
		fa = f(a);
		fb = f(b);
		fmid = f(mid);
		total = delta * (fa + fb);
		left = deltahalf * (fa + fmid);
		right = deltahalf * (fb + fmid);
		diff = total - (left + right);
		if (diff < 0.0)
			diff = -diff;
		if (diff < epsilon) {
			return new Double(total);
		} else if (diff > THRESHOLD) {
			/**
			 * Se agrega este bloque donde, si todavía no alcanzamos el 
			 * umbral, se ejecuta la recursión de manera concurrente.
			 */
			Future<Double> future1 = execute(mid, b, epsilon);
			Future<Double> future2 = execute(a, mid, epsilon);
			return future1.get() + future2.get();
		} else {
			return integrate(mid, b, epsilon) + integrate(a, mid, epsilon);
		}
	}

	public static void main(String[] args) {
		double a, b, epsilon;
		double res;
		/**
		 * Se mueve la creación del objeto hasta tener los valores 
		 * necesarios.
		 */
		//AdapInt adap = new AdapInt();
		if (args.length == 0) {
			a = 0.0;
			b = 64000.0;
			epsilon = 0.1;
		} else {
			if (args.length != 3) {
				System.out.println("Usage: adapint <start> <end> <epsilon>");
				System.out.println("Try: adapint 0 400 0.0001");
				System.exit(-6);
			}

			a = Double.parseDouble(args[0]);
			b = Double.parseDouble(args[1]);
			epsilon = Double.parseDouble(args[2]);
		}

		System.out.println("Starting integrate, from " + a + " to " + b
				+ " epsilon = " + epsilon);

		long start = System.currentTimeMillis();
		/**
		 * Ahora estoy en condiciones de crear el objeto con los parámetros
		 * correctos.
		 */
		AdapInt adap = new AdapInt(a, b, epsilon);
		try {
			/** Creo un pool simple donde agregar la tarea. */
			ExecutorService pool = Executors.newSingleThreadExecutor();
			/** Agrego la tarea */
			Future<Double> future = pool.submit(adap);
			/** Espero que el procesamiento termine */
			res = future.get();
		} catch (Exception e) {
			e.printStackTrace();
			res = -1;
		}

		long end = System.currentTimeMillis();
		double time = (end - start) / 1000.0;

		System.out.println("application time integrate (" + (int) a + ","
				+ (int) b + "," + epsilon + ") took " + time + " s");
		System.out.println("application result integrate (" + (int) a + ","
				+ (int) b + "," + epsilon + ") result = " + res);
		if (args.length == 0) {
			double diff = res - -5535.065343835229;

			if (diff > 0.0001 || diff < -0.0001) {
				System.out.println("Result should be -5535.065343835229");
				System.out.println("Test failed!");
				System.exit(1);
			}
		}
		/**
		 * Salgo con exit() porque sinó no sale.
		 */
		System.exit(0);
	}
}